﻿using System;
using System.Collections.Generic;
using System.Linq;
using CID.Domain.Audit;
using CID.Domain.Development;
using CID.Domain.Exceptions;
using CID.Domain.Infrastructure;
using CID.Domain.Operations;
using CID.Support;
using CID.Tests.Support;
using NUnit.Framework;
using Rhino.Mocks;

namespace CID.Domain.Tests.Audit
{
    [TestFixture]
    public class ReleaseDeploymentLogTests : BaseFunctionalTests
    {
        private const int VersionCount = 3;
        private const string DetailsMessage = "Deployment Message";

        private static IVersion BuildVersionStub(IRelease release, IEnvironment environment)
        {
            var component = Development.StubsBuilder.BuildComponentStub(activationDeactivationDefinition: Infrastructure.StubsBuilder.BuildActivationDeactivationDefinition());
            component.ActivationDeactivationDefinition.Stub(x => x.GetActivationParamaterValues(environment)).Return(new IParameterValue[0]);
            component.ActivationDeactivationDefinition.Stub(x => x.GetDeactivationParamaterValues(environment)).Return(new IParameterValue[0]);

            var result = Development.StubsBuilder.BuildVersionStub(component, release);
            var deploymentTemplate = Infrastructure.StubsBuilder.BuildDeploymentTemplateStub();
            var deploymentDefinition = Infrastructure.StubsBuilder.BuildDeploymentDefinitionStub(deploymentTemplate);
            deploymentDefinition.Stub(x => x.GetDeploymentParamaterValues(environment)).Return(new IParameterValue[0]);
            result.Stub(x => x.DeploymentDefinition).Return(deploymentDefinition);

            return result;
        }

        private static ReleaseDeploymentLog CreateDefaultReleaseDeploymentLog()
        {
            var environment = Operations.StubsBuilder.BuildEnvironmentStub();
            var release = Development.StubsBuilder.BuildReleaseStub();
            var versions = new List<IVersion>();
            release.Stub(x => x.Versions).Return(new IVersion[0]);
            var user = Users.StubsBuilder.BuildUserStub();

            return new ReleaseDeploymentLog(release, environment, user);
        }

        public override void SetUp()
        {
            base.SetUp();
            ApplicationDateTime.SetProvider(new FixedApplicationDateTimeProvider(DateTime.Now));
        }

        public override void TearDown()
        {
            ApplicationDateTime.ResetProvider();
            base.TearDown();
        }

        [Test]
        public void CreateReleaseDeploymentLog()
        {
            var environment = Operations.StubsBuilder.BuildEnvironmentStub();
            var release = Development.StubsBuilder.BuildReleaseStub();
            var versions = new List<IVersion>();
            for (int i = 0; i < VersionCount; i++)
                versions.Add(BuildVersionStub(release, environment));
            release.Stub(x => x.Versions).Return(versions);
            var user = Users.StubsBuilder.BuildUserStub();

            var releaseDeploymentLog = new ReleaseDeploymentLog(release, environment, user);

            Assert.AreEqual(release, releaseDeploymentLog.Release);
            Assert.AreEqual(environment, releaseDeploymentLog.Environment);
            Assert.AreEqual(user, releaseDeploymentLog.Responsible);
            Assert.AreEqual(DeploymentStatus.Unknown, releaseDeploymentLog.Status);
            Assert.IsNull(releaseDeploymentLog.StartTimestamp);
            Assert.IsNull(releaseDeploymentLog.EndTimestamp);
            Assert.IsEmpty(releaseDeploymentLog.Details);
            Assert.AreNotEqual(Guid.Empty, releaseDeploymentLog.DeploymentUniqueIdentifier);
            Assert.AreEqual(VersionCount, releaseDeploymentLog.Versions.Count());
            CollectionAssert.AllItemsAreInstancesOfType(releaseDeploymentLog.Versions, typeof(VersionDeploymentLog));
            CollectionAssert.AreEquivalent(versions, releaseDeploymentLog.Versions.Select(x => x.Version));
        }

        #region Start
        [Test]
        public void MarkStart_ReleaseDeploymentLogInUnknownStatus_ShouldSucceed()
        {
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();

            releaseDeploymentLog.Start();

            Assert.AreEqual(DeploymentStatus.Started, releaseDeploymentLog.Status);
            Assert.AreEqual(ApplicationDateTime.Now, releaseDeploymentLog.StartTimestamp);
        }

        [Test, ExpectedException(typeof(InvalidActionException))]
        public void MarkStart_ReleaseDeploymentLogInSartedStatus_ShouldThrowException()
        {
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();
            releaseDeploymentLog.SetProperty(x => x.Status, DeploymentStatus.Started);

            releaseDeploymentLog.Start();
        }

        [Test, ExpectedException(typeof(InvalidActionException))]
        public void MarkStart_ReleaseDeploymentLogInFailedStatus_ShouldThrowException()
        {
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();
            releaseDeploymentLog.SetProperty(x => x.Status, DeploymentStatus.Failed);

            releaseDeploymentLog.Start();
        }

        [Test, ExpectedException(typeof(InvalidActionException))]
        public void MarkStart_ReleaseDeploymentLogInSucceededStatus_ShouldThrowException()
        {
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();
            releaseDeploymentLog.SetProperty(x => x.Status, DeploymentStatus.Succeeded);

            releaseDeploymentLog.Start();
        }
        #endregion

        #region End
        [Test]
        public void MarkEndSuccessfull_ReleaseDeploymentLogInStartedStatus_ShouldSucceed()
        {
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();
            releaseDeploymentLog.SetProperty(x => x.Status, DeploymentStatus.Started);

            releaseDeploymentLog.End(true);

            Assert.AreEqual(DeploymentStatus.Succeeded, releaseDeploymentLog.Status);
            Assert.AreEqual(ApplicationDateTime.Now, releaseDeploymentLog.EndTimestamp);
        }

        [Test]
        public void MarkEndFailed_ReleaseDeploymentLogInStartedStatus_ShouldSucceed()
        {
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();
            releaseDeploymentLog.SetProperty(x => x.Status, DeploymentStatus.Started);

            releaseDeploymentLog.End(false);

            Assert.AreEqual(DeploymentStatus.Failed, releaseDeploymentLog.Status);
            Assert.AreEqual(ApplicationDateTime.Now, releaseDeploymentLog.EndTimestamp);
        }

        [Test, ExpectedException(typeof(InvalidActionException))]
        public void MarkEnd_ReleaseDeploymentLogInUnknownStatus_ShouldThrowException()
        {
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();
            

            releaseDeploymentLog.End(true);
        }

        [Test, ExpectedException(typeof(InvalidActionException))]
        public void MarkEnd_ReleaseDeploymentLogInFailedStatus_ShouldThrowException()
        {
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();
            releaseDeploymentLog.SetProperty(x => x.Status, DeploymentStatus.Failed);

            releaseDeploymentLog.End(true);
        }

        [Test, ExpectedException(typeof(InvalidActionException))]
        public void MarkEnd_ReleaseDeploymentLogInSucceededStatus_ShouldThrowException()
        {
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();
            releaseDeploymentLog.SetProperty(x => x.Status, DeploymentStatus.Succeeded);

            releaseDeploymentLog.End(true);
        }
        #endregion

        #region AppendMessage
        [Test, ExpectedException(typeof(InvalidActionException))]
        public void AppendDetails_ReleaseDeploymentLogInUnknownStatus_ShouldThrowException()
        {
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();

            releaseDeploymentLog.AppendDetails(DetailsMessage);
        }

        [Test]
        public void AppendDetails_ReleaseDeploymentLogInStartedStatus_ShouldSucceed()
        {
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();
            releaseDeploymentLog.SetProperty(x => x.Status, DeploymentStatus.Started);

            releaseDeploymentLog.AppendDetails(DetailsMessage);

            StringAssert.Contains(DetailsMessage, releaseDeploymentLog.Details);
        }

        [Test]
        public void AppendDetailsTwice_ReleaseDeploymentLogInStartedStatus_ShouldSucceed()
        {
            const string AnotherDetailsMessage = "Further Details";
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();
            releaseDeploymentLog.SetProperty(x => x.Status, DeploymentStatus.Started);

            releaseDeploymentLog.AppendDetails(DetailsMessage);
            releaseDeploymentLog.AppendDetails(AnotherDetailsMessage);

            StringAssert.Contains(DetailsMessage, releaseDeploymentLog.Details);
            StringAssert.Contains(AnotherDetailsMessage, releaseDeploymentLog.Details);
        }

        [Test, ExpectedException(typeof(InvalidActionException))]
        public void AppendDetails_ReleaseDeploymentLogInFailedStatus_ShouldThrowException()
        {
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();
            releaseDeploymentLog.SetProperty(x => x.Status, DeploymentStatus.Failed);

            releaseDeploymentLog.AppendDetails(DetailsMessage);
        }

        [Test, ExpectedException(typeof(InvalidActionException))]
        public void AppendDetails_ReleaseDeploymentLogInSucceededStatus_ShouldThrowException()
        {
            var releaseDeploymentLog = CreateDefaultReleaseDeploymentLog();
            releaseDeploymentLog.SetProperty(x => x.Status, DeploymentStatus.Succeeded);

            releaseDeploymentLog.AppendDetails(DetailsMessage);
        }
        #endregion
    }
}
